package com.kinyx.framework.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;

import com.kinyx.framework.annotation.assist.CanNotNull;
import com.kinyx.framework.annotation.assist.CanNull;
import com.kinyx.framework.annotation.assist.ReturnNull;
import com.kinyx.framework.annotation.assist.Tester;
import com.kinyx.framework.annotation.assist.Throws;
import com.kinyx.framework.exception.KRuntimeException;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/**
 * 列表工具类
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class KListUtils {

	/**
	 * 深度拷贝列表
	 * @param <T> 泛型
	 * @param list 源列表
	 * @return 新列表
	 */
	@SuppressWarnings("unchecked")
	@ReturnNull("list == null")
	@Throws(at = "KListUtils.deepCopy(List<T>)", value = "IOException", when = "new ObjectOutputStream(OutputStream)")
	@Throws(at = "KListUtils.deepCopy(List<T>)", value = "IOException", when = "ObjectOutputStream.writeObject(Object)")
	@Throws(at = "KListUtils.deepCopy(List<T>)", value = "IOException", when = "new ObjectInputStream(InputStream)")
	@Throws(at = "KListUtils.deepCopy(List<T>)", value = "ClassNotFoundException", when = "ObjectInputStream.readObject()")
	@Throws(at = "KListUtils.deepCopy(List<T>)", value = "IOException", when = "ObjectInputStream.readObject()")
	@Tester("com.kinyx.framework.utils.KListUtils_Tester_deepCopy")
	public static <T> List<T> deepCopy(@CanNull final List<T> list) {
		if (list == null) { return null; }
		if (list.isEmpty()) { return Collections.emptyList(); }

		ByteArrayOutputStream baos = null;
		ObjectOutputStream oos = null;
		ByteArrayInputStream bais = null;
		ObjectInputStream ois = null;

		try {
			baos = new ByteArrayOutputStream();

			try {
				oos = new ObjectOutputStream(baos);
			} catch (final IOException e) {
				throw new KRuntimeException(e);
			}

			try {
				oos.writeObject(list);
			} catch (final IOException e) {
				throw new KRuntimeException(e);
			}

			bais = new ByteArrayInputStream(baos.toByteArray());

			try {
				ois = new ObjectInputStream(bais);
			} catch (final IOException e) {
				throw new KRuntimeException(e);
			}

			try {
				return (List<T>) ois.readObject();
			} catch (final ClassNotFoundException e) {
				throw new KRuntimeException(e);
			} catch (final IOException e) {
				throw new KRuntimeException(e);
			}
		} finally {
			if (ois != null) {
				try {
					ois.close();
				} catch (final IOException e) {} // ignore this
			}
			if (bais != null) {
				try {
					bais.close();
				} catch (final IOException e) {} // ignore this
			}
			if (oos != null) {
				try {
					oos.close();
				} catch (final IOException e) {} // ignore this
			}
			if (baos != null) {
				try {
					baos.close();
				} catch (final IOException e) {} // ignore this
			}
		}
	}

	/**
	 * 将列表增加到列表中（去重）
	 * @param <T> 泛型
	 * @param listMain 源列表
	 * @param listAdd 新增列表
	 * @param comparator 去重过滤条件
	 * @return 新列表
	 */
	@ReturnNull("listMain == null && listAdd == null")
	@Tester("com.kinyx.framework.utils.KListUtils_Tester_distinct")
	public static <T> List<T> distinct(@CanNull final List<T> listMain, @CanNull final List<T> listAdd, @CanNull(defaultValue = "equals(T)") final Comparator<T> comparator) {
		if (comparator == null) {
			// new TreeSet<>(comparator); 会改变顺序，弃用。
			return distinct(listMain, listAdd, (t1, t2) -> {
				if (t1 == null) {
					return t2 == null ? 0 : -1;
				}
				return t1.equals(t2) ? 0 : 1;
			});
		}
		if (listMain == null) {
			if (listAdd == null) { return null; }
			if (listAdd.isEmpty()) { return Collections.emptyList(); }
			return new ArrayList<>(listAdd);
		}

		if (listMain.isEmpty()) {
			if ((listAdd == null) || listAdd.isEmpty()) { return Collections.emptyList(); }
			return new ArrayList<>(listAdd);
		}

		final List<T> list = new ArrayList<>(listMain);
		if ((listAdd == null) || listAdd.isEmpty()) { return list; }

		for (final T add : listAdd) {
			boolean flag = true;
			for (final T main : listMain) {
				if (comparator.compare(main, add) == 0) {
					flag = false;
					break;
				}
			}
			if (flag) {
				list.add(add);
			}
		}
		return list;
	}

	/**
	 * 转换列表
	 * @param <Source> 源泛型
	 * @param <Target> 目标泛型
	 * @param listSource 源列表
	 * @param converter 目标列表
	 * @return 新列表
	 */
	@ReturnNull("listSource == null")
	@Tester("com.kinyx.framework.utils.KListUtils_Tester_convert")
	public static <Source, Target> List<Target> convert(@CanNull final List<Source> listSource, @CanNotNull final Function<Source, Target> converter) {
		if (listSource == null) { return null; }
		if (listSource.isEmpty()) { return Collections.emptyList(); }
		final List<Target> listTarget = new ArrayList<>(listSource.size());
		listSource.forEach(source -> { listTarget.add(converter.apply(source)); });
		return listTarget;
	}

	/**
	 * 转换为数组
	 * @param list 列表
	 * @return 数组
	 */
	@ReturnNull("list == null")
	public static String[] toStringArray(@CanNull final List<String> list) {
		if (list == null) { return null; }
		if (list.isEmpty()) { return KArrayUtils.newArray(String.class); }
		final String[] arr = new String[list.size()];
		list.toArray(arr);
		return arr;
	}

	/**
	 * 转换为数组
	 * @param list 列表
	 * @return 数组
	 */
	@ReturnNull("list == null")
	public static Object[] toArray(@CanNull final List<?> list) {
		if (list == null) { return null; }
		if (list.isEmpty()) { return KArrayUtils.newArray(Object.class); }
		final Object[] arr = new Object[list.size()];
		list.toArray(arr);
		return arr;
	}

	/**
	 * 转换为字符串
	 * @param list 列表
	 * @return 字符串
	 */
	@ReturnNull("list == null")
	public static String toString(@CanNull final List<?> list) {
		if (list == null) { return null; }
		return KArrayUtils.toString(toArray(list));
	}

	/**
	 * 转换为字符串
	 * @param list 列表
	 * @param escape true：将每一个字符串元素前后都增加双引号；false：不做特殊处理；
	 * @return 字符串
	 */
	@ReturnNull("list == null")
	public static String toString(@CanNull final List<String> list, @CanNotNull final boolean escape) {
		if (list == null) { return null; }
		return KArrayUtils.toString(toStringArray(list), escape);
	}

	/**
	 * 拼接
	 * @param <T> 泛型
	 * @param list 列表
	 * @param separator 分隔符
	 * @param converter 转换器
	 * @return 拼接结果
	 */
	@ReturnNull("list == null")
	@Tester("com.kinyx.framework.utils.KListUtils_Tester_join")
	public static <T> String join(@CanNull final List<T> list, @CanNull(defaultValue = ",") final String separator, @CanNull(defaultValue = "toString()") final BiFunction<T, Integer, String> converter) {
		if (list == null) { return null; }
		if (converter == null) { return join(list, separator, (value, index) -> String.valueOf(value)); }
		if (separator == null) { return join(list, ",", converter); }
		final int iMax = list.size() - 1;
		if (iMax == -1) { return ""; }
		final StringBuilder sb = new StringBuilder();
		for (int i = 0;; i++) {
			sb.append(converter.apply(list.get(i), i));
			if (i == iMax) {
				return sb.toString();
			}
			sb.append(separator);
		}
	}

}
